;; ollama-mode.el
;; 自动学习工作流：与Ollama交互，获取Elisp代码，执行测试和单元测试
;; (add-to-list 'load-path "~/.emacs.d/ollama-mode")
;; (require 'ollama-mode)

(require 'url)
(require 'json)

;; 全局变量定义
(defvar ollama-mode-api-url "http://localhost:11434/api/generate"
  "The API endpoint for Ollama server.")

(defvar ollama-mode-model "qwen3:8b"
  "The model used for Ollama. Using code-specific model for better Elisp generation.")

(defvar ollama-mode-buffer-name "*Ollama Auto Learning*"
  "The name of the buffer for Ollama自动学习模式.")

(defvar ollama-mode-elisp-save-directory "~/.emacs.d/ollama-auto-learning/"
  "Directory to store generated Elisp code.")

(defvar ollama-mode-test-directory "~/.emacs.d/ollama-auto-learning/tests/"
  "Directory to store generated test files.")

(defvar ollama-mode-max-retries 5
  "Maximum number of retries to get valid Elisp code.")

(defvar ollama-mode-learning-objectives '()
  "List of learning objectives for the AI.")

(defvar ollama-mode-current-objective nil
  "Current learning objective being processed.")

(defvar ollama-mode-execution-history '()
  "History of code executions and results.")

(defvar ollama-mode-is-auto-learning nil
  "Whether auto-learning mode is active.")

(defvar ollama-mode-learning-timer nil
  "Timer for auto-learning process.")

(defvar ollama-mode-test-results '()
  "Storage for test results.")

;; 增强的系统提示词 - 要求返回可测试的完整代码
(defconst ollama-mode-system-prompt "
你是一个Emacs Lisp专家和自动学习系统。请严格按照以下要求工作：

**核心要求：**
1. 必须返回完整的、可独立执行的Emacs Lisp代码
2. 代码必须包含完整的函数定义和文档字符串
3. 代码必须包含单元测试函数
4. 所有代码必须用```elisp和```包裹

**代码结构要求：**
- 主函数定义（解决用户问题的函数）
- 辅助函数（如果需要）
- 单元测试函数（以-test结尾，如my-function-test）
- 测试运行函数（run-tests）

**单元测试要求：**
- 每个主要函数都要有对应的测试函数
- 测试函数要覆盖正常情况和边界情况
- 使用`ert-deftest`或自定义测试框架
- 测试函数应该返回t表示通过，nil表示失败

**代码质量标准：**
- 每个函数都要有完整的定义和文档字符串
- 包含适当的错误处理
- 代码要有实用性
- 遵循Emacs Lisp编码规范
- 确保所有变量和函数都有定义

**重要规则：**
- 不要返回不完整的代码片段
- 不要引用未定义的变量或函数
- 确保代码可以独立运行而不依赖外部状态
- 必须包含可执行的测试代码

**示例格式：**
```elisp
;; 主函数
(defun my-function (arg)
  \"文档字符串\"
  (interactive)
  ;; 实现代码
  )

;; 单元测试
(defun my-function-test ()
  \"测试my-function\"
  (and (equal (my-function input1) expected1)
       (equal (my-function input2) expected2)))

;; 测试运行器
(defun run-tests ()
  \"运行所有测试\"
  (interactive)
  (if (my-function-test)
      (message \"所有测试通过!\")
    (message \"某些测试失败!\")))

执行反馈：
你的代码将被自动执行和测试，测试结果会反馈给你用于改进。
")

;; 确保目录存在
(defun ollama-mode--ensure-directories ()
"Ensure all necessary directories exist."
(unless (file-exists-p ollama-mode-elisp-save-directory)
(make-directory ollama-mode-elisp-save-directory t))
(unless (file-exists-p ollama-mode-test-directory)
(make-directory ollama-mode-test-directory t)))

;; 生成唯一的文件名
(defun ollama-mode--generate-filename (objective &optional type attempt)
"Generate a unique filename based on OBJECTIVE and timestamp."
(let ((timestamp (format-time-string "%Y%m%d-%H%M%S"))
(clean-objective (replace-regexp-in-string "[^a-zA-Z0-9]" "-" objective))
(type-suffix (if type (format "-%s" type) ""))
(attempt-suffix (if attempt (format "-attempt-%d" attempt) "")))
(concat (if (equal type "test") ollama-mode-test-directory ollama-mode-elisp-save-directory)
"auto-learn-" timestamp attempt-suffix type-suffix "-"
(substring clean-objective 0 (min 30 (length clean-objective)))
".el")))

;; 保存 Elisp 代码到文件
(defun ollama-mode--save-elisp-code (elisp-code objective execution-result test-results)
"Save ELISP-CODE to a file with metadata."
(ollama-mode--ensure-directories)
(let ((filename (ollama-mode--generate-filename objective "main")))
(with-temp-file filename
(insert ";; Auto-generated by Ollama Auto Learning System\n")
(insert ";; Objective: " objective "\n")
(insert ";; Timestamp: " (format-time-string "%Y-%m-%d %H:%M:%S") "\n")
(insert ";; Execution Result: " execution-result "\n")
(insert ";; Test Results: " test-results "\n")
(insert ";; File: " (file-name-nondirectory filename) "\n\n")
(insert elisp-code)
(insert "\n"))

;; 记录到执行历史
(push `((objective . ,objective)
        (filename . ,filename)
        (code . ,elisp-code)
        (result . ,execution-result)
        (test-results . ,test-results)
        (timestamp . ,(format-time-string "%Y-%m-%d %H:%M:%S")))
      ollama-mode-execution-history)

filename))

;; 验证是否为有效的 Elisp 代码
(defun ollama-mode--valid-elisp-p (code)
"Check if CODE is valid Elisp code."
(condition-case nil
(progn
(read code)
t)
(error nil)))

;; 检查代码完整性
(defun ollama-mode--check-code-completeness (code)
"Check if CODE is complete and self-contained."
(with-temp-buffer
(insert code)
(goto-char (point-min))

(let ((has-function-def nil)
      (has-test-function nil)
      (has-run-tests nil)
      (unresolved-symbols '()))
  
  ;; 检查函数定义
  (when (re-search-forward "(defun\\s-+\\(\\sw+\\)" nil t)
    (setq has-function-def t))
  
  ;; 检查测试函数
  (goto-char (point-min))
  (when (re-search-forward "(defun\\s-+\\(\\sw+-test\\>\\)" nil t)
    (setq has-test-function t))
  
  ;; 检查测试运行函数
  (goto-char (point-min))
  (when (re-search-forward "(defun\\s-+\\(run-.*-tests?\\>\\)" nil t)
    (setq has-run-tests t))
  
  ;; 检查未解析的符号
  (goto-char (point-min))
  (while (re-search-forward "(\\s-*\\(\\sw+\\)\\s-+" nil t)
    (let ((symbol (match-string 1)))
      (unless (or (member symbol '("defun" "defvar" "defconst" "setq" "let" "if" "when" "unless" "progn" "cond" "case" "and" "or" "not" "while" "dolist" "dotimes" "lambda" "function" "quote" "message" "format" "insert" "print" "princ" "terpri" "error" "signal" "condition-case" "unwind-protect" "ert-deftest" "should" "should-not" "equal" "=" "eq" "eql"))
                  (re-search-backward (format "(defun\\s-+%s" symbol) nil t))
        (push symbol unresolved-symbols))))
  
  (list :complete (and (not unresolved-symbols) has-function-def)
        :has-tests (and has-test-function has-run-tests)
        :unresolved-symbols unresolved-symbols))))


;; 提取 Elisp 代码块
(defun ollama-mode--extract-elisp-code (response)
"Extract Elisp code from RESPONSE."
(with-temp-buffer
(insert response)
(goto-char (point-min))
(let (code-blocks)
;; 查找 elisp 或lisp 代码块
(while (re-search-forward "\\(elisp\\|lisp\\)?\n\\(.*?\\)\n" nil t)
(push (match-string 2) code-blocks))
;; 如果没有代码块，尝试查找 ( 开头的表达式
(when (null code-blocks)
(goto-char (point-min))
(while (re-search-forward "(\([^)]+\|\n\)*)" nil t)
(push (match-string 0) code-blocks)))
;; 返回最长的代码块
(when code-blocks
(car (sort code-blocks (lambda (a b) (> (length a) (length b)))))))))

;; 安全执行 Elisp 代码
(defun ollama-mode--execute-elisp-code (elisp-code)
"Safely execute ELISP-CODE and return the result."
(condition-case err
(progn
;; 先编译代码确保没有语法错误
(read elisp-code)
;; 然后执行
(let ((result (eval (read elisp-code))))
(format "Success: %S" result)))
(error
(format "Error: %s" (error-message-string err)))))

;; 运行测试函数
(defun ollama-mode--run-tests (elisp-code)
"Run test functions in ELISP-CODE and return test results."
(condition-case err
(progn
;; 评估代码以定义函数
  (eval (read elisp-code))

    (let ((test-results '())
          (test-functions '())
          (run-test-function nil))
      
      ;; 查找所有-test函数和run-tests函数
      (with-temp-buffer
        (insert elisp-code)
        (goto-char (point-min))
        (while (re-search-forward "(defun\\s-+\\(\\sw+-test\\>\\)" nil t)
          (push (match-string 1) test-functions))
        (goto-char (point-min))
        (when (re-search-forward "(defun\\s-+\\(run-.*-tests?\\>\\)" nil t)
          (setq run-test-function (match-string 1))))
      
      ;; 运行单个测试函数
      (dolist (test-func test-functions)
        (condition-case test-err
            (let ((result (funcall (intern test-func))))
              (push (list :test test-func :result result :status (if result 'passed 'failed)) test-results))
          (error
           (push (list :test test-func :result (error-message-string test-err) :status 'error) test-results))))
      
      ;; 运行测试套件（如果有）
      (when run-test-function
        (condition-case suite-err
            (let ((result (funcall (intern run-test-function))))
              (push (list :test-suite run-test-function :result result :status (if result 'passed 'failed)) test-results))
          (error
           (push (list :test-suite run-test-function :result (error-message-string suite-err) :status 'error) test-results))))
      
      ;; 如果没有找到测试函数，创建默认测试
      (when (null test-results)
        (push (list :test "no-tests" :result "No test functions found" :status 'warning) test-results))
      
      test-results))
(error 
 (list (list :test "test-execution" :result (error-message-string err) :status 'error)))))

;; 分析测试结果
(defun ollama-mode--analyze-test-results (test-results)
"Analyze TEST-RESULTS and return summary."
(let ((passed 0)
(failed 0)
(errors 0)
(warnings 0))

 (dolist (result test-results)
  (pcase (plist-get result :status)
    ('passed (setq passed (1+ passed)))
    ('failed (setq failed (1+ failed)))
    ('error (setq errors (1+ errors)))
    ('warning (setq warnings (1+ warnings)))))

(list :total (+ passed failed errors warnings)
      :passed passed
      :failed failed
      :errors errors
      :warnings warnings
      :all-passed (and (> (+ passed failed errors warnings) 0) (= failed 0) (= errors 0)))))


;; 生成测试报告
(defun ollama-mode--generate-test-report (test-results)
"Generate a readable test report from TEST-RESULTS."
(let ((summary (ollama-mode--analyze-test-results test-results)))
(with-temp-buffer
(insert "=== 测试报告 ===\n\n")
(insert (format "总测试数: %d\n" (plist-get summary :total)))
(insert (format "通过: %d\n" (plist-get summary :passed)))
(insert (format "失败: %d\n" (plist-get summary :failed)))
(insert (format "错误: %d\n" (plist-get summary :errors)))
(insert (format "警告: %d\n" (plist-get summary :warnings)))
(insert (format "所有测试通过: %s\n\n" (if (plist-get summary :all-passed) "是" "否")))

  (insert "详细结果:\n")
  (dolist (result test-results)
    (insert (format "- %s: %s (%s)\n" 
                   (plist-get result :test)
                   (plist-get result :result)
                   (plist-get result :status))))
  
  (buffer-string))))

;; 改进的 API 调用函数
(defun ollama-mode--call-api (prompt &optional retry-count previous-results)
"Call the Ollama API with PROMPT and return valid Elisp code."
(let ((retry-count (or retry-count 0))
      (previous-results (or previous-results "")))

(when (>= retry-count ollama-mode-max-retries)
  (error "Maximum retries (%d) reached. Could not get valid Elisp code." ollama-mode-max-retries))

(let* ((enhanced-prompt 
        (if previous-results
            (format "PROBLEM: Previous code failed tests or had errors.\n\nOriginal request: %s\n\nPrevious issues: %s\n\nPlease provide COMPLETE, TESTABLE Elisp code with proper unit tests."
                    prompt previous-results)
          (format "%s\n\nIMPORTANT: Return COMPLETE, TESTABLE Elisp code with unit tests for all major functions." prompt)))
       
       (url-request-method "POST")
       (url-request-extra-headers
        '(("Content-Type" . "application/json")))
       (url-request-data
        (encode-coding-string 
         (json-encode
          `((model . ,ollama-mode-model)
            (stream . :json-false)
            (system . ,ollama-mode-system-prompt)
            (prompt . ,enhanced-prompt)))
         'utf-8))
       (buffer (url-retrieve-synchronously ollama-mode-api-url)))
  
  (if buffer
      (with-current-buffer buffer
        (goto-char (point-min))
        (re-search-forward "^$")
        (let* ((json-string (decode-coding-string
                             (buffer-substring (point) (point-max))
                             'utf-8))
               (json-data (json-read-from-string json-string))
               (response (alist-get 'response json-data)))
          
          (kill-buffer buffer)
          
          (if response
              (let ((elisp-code (ollama-mode--extract-elisp-code response)))
                (if (and elisp-code (ollama-mode--valid-elisp-p elisp-code))
                    (let ((completeness-check (ollama-mode--check-code-completeness elisp-code)))
                      (if (plist-get completeness-check :complete)
                          (progn
                            (message "✓ Successfully extracted complete Elisp code with tests (attempt %d)" (1+ retry-count))
                            (cons elisp-code response))
                        (progn
                          (message "⚠ Incomplete code (unresolved: %s), retrying... (attempt %d)" 
                                   (plist-get completeness-check :unresolved-symbols) (1+ retry-count))
                          (sleep-for 2)
                          (ollama-mode--call-api prompt (1+ retry-count) previous-results))))
                  (progn
                    (message "⚠ Invalid Elisp code, retrying... (attempt %d)" (1+ retry-count))
                    (sleep-for 2)
                    (ollama-mode--call-api prompt (1+ retry-count) previous-results))))
            (error "Error: No response from Ollama"))))
    (error "Error: Failed to connect to Ollama server")))))

;; 显示处理结果
(defun ollama-mode--display-processing-result (objective code result test-report saved-filename)
"Display processing results in the buffer."
(let ((buffer (get-buffer ollama-mode-buffer-name)))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(let ((inhibit-read-only t))
(goto-char (point-max))
(insert (format "\n\n%s Processing Objective: %s %s\n"
(make-string 20 ?=) objective (make-string 20 ?=)))
(insert (format "Generated Elisp Code:\n%s\n\n" code))
(insert (format "Execution Result: %s\n" result))
(insert (format "Test Report:\n%s\n" test-report))
(insert (format "Saved to: %s\n" saved-filename))
(insert (make-string 60 ?-))
(goto-char (point-max)))))))

;; 单个学习目标的完整处理流程
(defun ollama-mode--process-objective (objective)
"Process a single learning OBJECTIVE: generate, execute, test, and feedback."
(message "Processing objective: %s" objective)

(condition-case err
(let* ((api-result (ollama-mode--call-api objective))
(elisp-code (car api-result))
(raw-response (cdr api-result))
(execution-result (ollama-mode--execute-elisp-code elisp-code))
(test-results (ollama-mode--run-tests elisp-code))
(test-report (ollama-mode--generate-test-report test-results))
(test-summary (ollama-mode--analyze-test-results test-results))
(saved-filename (ollama-mode--save-elisp-code elisp-code objective execution-result test-report)))

    ;; 存储测试结果
    (push `((objective . ,objective)
            (code . ,elisp-code)
            (test-results . ,test-results)
            (test-summary . ,test-summary)
            (timestamp . ,(format-time-string "%Y-%m-%d %H:%M:%S")))
          ollama-mode-test-results)
    
    ;; 显示处理结果
    (ollama-mode--display-processing-result objective elisp-code execution-result test-report saved-filename)
    
    ;; 如果测试失败，尝试改进代码
    (unless (plist-get test-summary :all-passed)
      (message "Tests failed, requesting improved code...")
      (let* ((improved-prompt (format "The Elisp code for '%s' failed tests. Test report: %s\n\nPlease provide CORRECTED code that passes all tests."
                                     objective test-report))
             (improved-api-result (ollama-mode--call-api improved-prompt))
             (improved-elisp-code (car improved-api-result))
             (improved-result (ollama-mode--execute-elisp-code improved-elisp-code))
             (improved-test-results (ollama-mode--run-tests improved-elisp-code))
             (improved-test-report (ollama-mode--generate-test-report improved-test-results)))
        
        (ollama-mode--save-elisp-code improved-elisp-code 
                                     (format "IMPROVED: %s" objective)
                                     improved-result
                                     improved-test-report)
        
        ;; 显示改进结果
        (let ((buffer (get-buffer ollama-mode-buffer-name)))
          (when (buffer-live-p buffer)
            (with-current-buffer buffer
              (let ((inhibit-read-only t))
                (goto-char (point-max))
                (insert (format "\n%s Improvement Attempt %s\n" 
                                (make-string 15 ?*) (make-string 15 ?*)))
                (insert (format "Improved Code:\n%s\n\n" improved-elisp-code))
                (insert (format "Improved Execution Result: %s\n" improved-result))
                (insert (format "Improved Test Report:\n%s\n" improved-test-report))
                (insert (make-string 60 ?-))
                (goto-char (point-max))))))))
    
    ;; 返回成功状态
    (plist-get test-summary :all-passed))

(error 
 (message "Failed to process objective '%s': %s" objective (error-message-string err))
 nil)))

;; 自动学习工作流
(defun ollama-mode--auto-learning-workflow ()
"Main auto-learning workflow: process all objectives sequentially."
(when (and ollama-mode-is-auto-learning ollama-mode-learning-objectives)
(let ((objective (car ollama-mode-learning-objectives)))
(setq ollama-mode-current-objective objective)
(setq ollama-mode-learning-objectives (cdr ollama-mode-learning-objectives))

  (message "Auto-learning: %s (%d remaining)" 
           objective (length ollama-mode-learning-objectives))
  
  (if (ollama-mode--process-objective objective)
      (progn
        (message "✓ Completed with all tests passing: %s" objective)
        (when ollama-mode-learning-objectives
          ;; 继续处理下一个目标
          (run-with-timer 3 nil 'ollama-mode--auto-learning-workflow)))
    (progn
      (message "✗ Failed or tests not passing: %s" objective)
      (when ollama-mode-learning-objectives
        (run-with-timer 3 nil 'ollama-mode--auto-learning-workflow)))))))

;; 用户界面函数
(defun ollama-mode-add-objective (objective)
"Add a learning OBJECTIVE to the queue."
(interactive "sEnter learning objective: ")
(push objective ollama-mode-learning-objectives)
(message "Added objective: %s (%d total)" objective (length ollama-mode-learning-objectives)))

(defun ollama-mode-start-auto-learning ()
"Start the auto-learning process."
(interactive)
(when (null ollama-mode-learning-objectives)
(error "No learning objectives defined. Use ollama-mode-add-objective first."))

(setq ollama-mode-is-auto-learning t)
(ollama-mode--setup-buffer)
(switch-to-buffer (get-buffer-create ollama-mode-buffer-name))

(message "Starting auto-learning with %d objectives..." (length ollama-mode-learning-objectives))
(ollama-mode--auto-learning-workflow))

(defun ollama-mode-stop-auto-learning ()
"Stop the auto-learning process."
(interactive)
(setq ollama-mode-is-auto-learning nil)
(when ollama-mode-learning-timer
(cancel-timer ollama-mode-learning-timer)
(setq ollama-mode-learning-timer nil))
(message "Auto-learning stopped. %d objectives remaining." (length ollama-mode-learning-objectives)))

(defun ollama-mode-clear-objectives ()
"Clear all learning objectives."
(interactive)
(setq ollama-mode-learning-objectives '())
(message "All learning objectives cleared."))

(defun ollama-mode-list-objectives ()
"List all current learning objectives."
(interactive)
(if ollama-mode-learning-objectives
(progn
(switch-to-buffer (get-buffer-create "Ollama Objectives"))
(erase-buffer)
(insert "Current Learning Objectives:\n\n")
(dolist (obj ollama-mode-learning-objectives)
(insert (format "• %s\n" obj)))
(special-mode))
(message "No learning objectives defined.")))

;; 修复缓冲区设置函数
(defun ollama-mode--setup-buffer ()
"Initialize the Ollama auto-learning buffer."
(let ((buffer (get-buffer-create ollama-mode-buffer-name)))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(insert "=== Ollama Auto Learning Workflow with Testing ===\n\n")
(insert "Status: " (if ollama-mode-is-auto-learning "ACTIVE" "INACTIVE") "\n")
(insert "Model: " ollama-mode-model "\n")
(insert "Save Directory: " ollama-mode-elisp-save-directory "\n")
(insert "Test Directory: " ollama-mode-test-directory "\n")
(insert "Objectives Queued: " (number-to-string (length ollama-mode-learning-objectives)) "\n")
(insert "Execution History: " (number-to-string (length ollama-mode-execution-history)) " items\n")
(insert "Test Results: " (number-to-string (length ollama-mode-test-results)) " items\n\n")
(insert (make-string 60 ?=) "\n")
(setq buffer-read-only t)
buffer))))

;; 主入口函数
(defun ollama-auto-learning-mode ()
"Enter Ollama auto-learning mode."
(interactive)
(ollama-mode--ensure-directories)
(let ((buffer (ollama-mode--setup-buffer)))
(switch-to-buffer buffer)
(message "Ollama Auto Learning Mode with Testing ready. Add objectives with ollama-mode-add-objective")))

;; 查看测试结果
(defun ollama-mode-show-test-results ()
"Show detailed test results."
(interactive)
(if ollama-mode-test-results
(progn
(switch-to-buffer (get-buffer-create "Ollama Test Results"))
(erase-buffer)
(insert "Ollama Auto Learning Test Results\n\n")
(dolist (result ollama-mode-test-results)
(insert (format "Objective: %s\n" (alist-get 'objective result)))
(insert (format "Timestamp: %s\n" (alist-get 'timestamp result)))
(let ((summary (alist-get 'test-summary result)))
(insert (format "Tests Passed: %d/%d\n"
(plist-get summary :passed)
(plist-get summary :total))))
(insert (make-string 40 ?-) "\n"))
(special-mode))
(message "No test results yet.")))

;; 查看执行历史
(defun ollama-mode-show-history ()
"Show execution history."
(interactive)
(if ollama-mode-execution-history
(progn
(switch-to-buffer (get-buffer-create "Ollama Execution History"))
(erase-buffer)
(insert "Ollama Auto Learning Execution History\n\n")
(dolist (item ollama-mode-execution-history)
(insert (format "Objective: %s\n" (alist-get 'objective item)))
(insert (format "Timestamp: %s\n" (alist-get 'timestamp item)))
(insert (format "Result: %s\n" (alist-get 'result item)))
(insert (format "Test Results: %s\n" (alist-get 'test-results item)))
(insert (format "File: %s\n" (alist-get 'filename item)))
(insert (make-string 40 ?-) "\n"))
(special-mode))
(message "No execution history yet.")))

;; 提供命令
(provide 'ollama-mode)
